﻿using AutoMapper;
using ManagementApi.Authorizers;
using ManagementApi.Models;
using ManagementApi.Models.Manage.ProjectCorp;
using ManagementApi.Models.Manage.ProjectGroupMember;
using ManagementApi.Models.Payroll;
using ManagementApi.Models.System.AuditLog;
using ManagementApi.Mvc;
using ManagementApi.Mvc.ApplicationContexts;
using ManagementApi.Mvc.Filters;
using ManagementApi.Services;
using ManagementApi.Services.Import;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Modules;
using Modules.Attachment;
using Modules.Audit;
using Modules.BankSpecialAccount;
using Modules.Corp;
using Modules.Payroll;
using Modules.Project;
using Modules.SMS;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using NPOI.XSSF.UserModel;
using PmSoft;
using PmSoft.Exceptions;
using PmSoft.Utilities;
using static Modules.Payroll.WageConstants;
using static Modules.System.Flow.FlowConstants;

namespace ManagementApi.Controllers.ConstructorPannel
{
    /// <summary>
    /// 工资单相关接口
    /// </summary>
    [Route("/api/v1/constructor/payroll")]
    [ApiController]
    [Authorize(Roles = "Constructor")]
    [TypeFilter(typeof(RequireSelProjectActionFilter))]
    public class PayrollController : ControllerBase
    {
        private readonly WagePayrollService wagePayrollService;
        private readonly ConstructorApplicationContext applicationContext;
        private readonly SysAuditLogService sysAuditLogService;
        private readonly SerialNumberService serialNumberService;
        private readonly ProjectService projectService;
        private readonly ProjectGroupMemberService projectGroupMemberService;
        private readonly ImportService importService;
        private readonly WagePayrollDetailService payrollDetailService;
        private readonly IMapper mapper;
        private readonly ProjectInfo? projectInfo;
        private readonly ExportService exportService;
        private readonly ProjectOwnAuthorizer ownAuthorizer;
        private readonly BankSpecialAccountService bankSpecialAccountService;
        private readonly SmsSendLogService smsSendLogService;
        private readonly WageAttachmentService wageAttachmentService;

        public PayrollController(WagePayrollService wagePayrollService, WagePayrollDetailService payrollDetailService, ConstructorApplicationContext applicationContext, SysAuditLogService sysAuditLogService, ImportService importService, SerialNumberService serialNumberService, ProjectGroupMemberService projectGroupMemberService, ProjectService projectService, ExportService exportService, IMapper mapper, ProjectOwnAuthorizer ownAuthorizer, BankSpecialAccountService bankSpecialAccountService, SmsSendLogService smsSendLogService, WageAttachmentService wageAttachmentService)
        {
            this.wagePayrollService = wagePayrollService;
            this.payrollDetailService = payrollDetailService;
            this.applicationContext = applicationContext;
            this.sysAuditLogService = sysAuditLogService;
            this.serialNumberService = serialNumberService;
            this.projectService = projectService;
            this.projectGroupMemberService = projectGroupMemberService;
            this.exportService = exportService;
            this.importService = importService;
            this.mapper = mapper;
            this.bankSpecialAccountService = bankSpecialAccountService;
            this.smsSendLogService = smsSendLogService;
            this.wageAttachmentService= wageAttachmentService;
            projectInfo = projectService.GetProjectInfo(applicationContext.CurrentProjectInfo.ProjectId);
            this.ownAuthorizer = ownAuthorizer;
        }

        /// <summary>
        /// 工资单表单数据
        /// </summary>
        /// <param name="id">角色ID</param>
        /// <returns></returns>
        [HttpGet("{id}/form")]
        public ApiResult<PayrollEditForm> GetWagePayroll(int id)
        {
            PayrollEditForm form = new PayrollEditForm();
            if (id == 0)
            {
                return ApiResult<PayrollEditForm>.Success(form);
            }
            WagePayroll? data = wagePayrollService.Get(id);
            if (data == null)
                return ApiResult<PayrollEditForm>.Failure("工资单不存在");
            mapper.Map(data, form);
            form.CorpName = applicationContext.GetService<CorpService>().GetCorpInfo(data.CorpId)?.Name;
            if (form.EndDate.Date == DateTime.Now.Date)
            {
                form.EndDate = DateTime.Now.AddDays(-1).Date;
                form.Name = $"【{form.StartDate.ToString("yyyy-MM-01")}至{form.EndDate.ToString("yyyy-MM-dd")}_按{form.PayType.GetDescription()}发】工资单";
            }
            return ApiResult<PayrollEditForm>.Success(form);
        }

        /// <summary>
        /// 获取工资单列表(分页)
        /// </summary>
        /// <param name="query">查询条件</param>
        /// <param name="pageNum">页码</param>
        /// <param name="pageSize">每页数量</param>
        /// <returns></returns>
        [HttpGet("page")]
        public ApiResult<PageDto<PayrollDto>> GetWagePayrolls([FromQuery] WagePayrollQuery query, int pageNum = 1, int pageSize = 20)
        {
            query.ProjectId = applicationContext.CurrentProjectInfo.ProjectId;
            var data = wagePayrollService.GetWagePayrolls(query, pageNum, pageSize);
            var dtos = data.AsPageDto(source => { return source.AsDto(FlowEngine.建设单位提交, FlowEngine.建设单位审核); });
            return ApiResult<PageDto<PayrollDto>>.Success(dtos);
        }

        /// <summary>
        /// 删除工资单
        /// </summary>
        /// <param name="ids"></param>
        /// <returns></returns>
        [HttpDelete("{ids}")]
        public ApiResult Delete(string ids)
        {
            var datas = wagePayrollService.GetWagePayrolls(ValueUtility.ConvertToList<int>(ids));
            ownAuthorizer.CheckAndThrowIfOwnAuthenticationFailed(datas.Select(i => i.ProjectId));
            wagePayrollService.Deleted(ValueUtility.ConvertToList<int>(ids));
            return ApiResult.Success("删除成功");
        }

        /// <summary>
        /// 查询审核日志
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("auditLogs")]
        public ApiResult<IEnumerable<AuditLogDto>> GetSysAuditLogs(int id)
        {
            var data = sysAuditLogService.GetAuditLogsByObjectId(id, "wage_payroll");
            IEnumerable<AuditLogDto> auditLogDtos = data.AsDto();
            return ApiResult<IEnumerable<AuditLogDto>>.Success(auditLogDtos);
        }

        /// <summary>
        /// 审核
        /// </summary>
        /// <param name="id"></param>
        /// <param name="auditForm"></param>
        /// <returns></returns>
        [HttpPut("audit/{id}")]
        public ApiResult Audit(int id, WageAuditForm auditForm)
        {
            if (string.IsNullOrEmpty(auditForm.VerifyCode))
                return ApiResult.Failure("短信验证码不能为空");

            var data = wagePayrollService.Get(id) ?? throw new BusinessException("工资单不存在");

            #region 验证数据权限

            ownAuthorizer.CheckAndThrowIfOwnAuthenticationFailed(data.ProjectId);

            #endregion 验证数据权限

            var account= bankSpecialAccountService.GetBankSpecialAccountByProjectId(data.ProjectId)??throw new BusinessException("专户不存在");

            if (!smsSendLogService.Validate(account.ConstructorMobile, auditForm.VerifyCode, MsgType.VerificationCode))
                return ApiResult.Failure("短信验证码错误或已过期");

            wagePayrollService.Audit(id, auditForm.AuditIdea, auditForm.AuditIdeaContent, FlowEngine.建设单位审核);
            return ApiResult.Success("审核成功");
        }

        /// <summary>
        /// 工资单明细数据
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("details/{id}")]
        public ApiResult<PayrollDetailDto> Details(int id)
        {
            WagePayroll? wagePayroll = wagePayrollService.Get(id);
            if (wagePayroll == null) return ApiResult<PayrollDetailDto>.Failure("工资单信息不存在，请先申请工资单");
            IEnumerable<WagePayrollDetail> details = payrollDetailService.GetPayrollDetailsByPayrollId(id);
            PayrollDetailDto dto = new();
            if (!details.Any())
                //无明细数据,查询当前项目参建企业的班组及成员
                dto = projectGroupMemberService.GetProjectGroupMembersByCorpIdsAndProjectId(ValueUtility.ConvertToList<int>(wagePayroll.CorpId.ToString()), applicationContext.CurrentProjectInfo.ProjectId).Where(a => a.StartDate <= wagePayroll.EndDate).AsProjectGroupMemberDtos().AsPayrollDetailDto(wagePayroll);
            else
            {
                //有明细数据
                dto = details.AsPayrollDetailDto(wagePayroll, applicationContext);
                #region 附件查询

                var attachments = wageAttachmentService.GetSysAttachmentByObjectIdAndType(id, 工资附件类型.工资单凭证附件, 工资附件租户.WagePayroll);
                List<AttachmentsForm<int>> attach = new();
                foreach (var item in attachments)
                {
                    AttachmentsForm<int> attachmentsForm = new()
                    {
                        AttachmentId = item.Id,
                        AttachmentName = item.FriendlyName,
                    };
                    attach.Add(attachmentsForm);
                }
                dto.Attachments = attach;

                #endregion 附件查询
            }
            return ApiResult<PayrollDetailDto>.Success(dto);
        }

        /// <summary>
        /// 导出工资明细模板
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("exportTemplate/{id}")]
        public IActionResult ExportTemplate(int id)
        {
            WagePayroll? wagePayroll = wagePayrollService.Get(id);
            if (wagePayroll == null) throw new BusinessException("工资单信息不存在，请先申请工资单");
            wagePayrollService.Verify(wagePayroll);
            PayrollDetailDto dto = new();
            //查询当前项目参建企业的班组及成员
            dto = projectGroupMemberService.GetProjectGroupMembersByCorpIdsAndProjectId(ValueUtility.ConvertToList<int>(wagePayroll.CorpId.ToString()), applicationContext.CurrentProjectInfo.ProjectId).Where(a => a.StartDate <= wagePayroll.EndDate).AsProjectGroupMemberDtos().AsPayrollDetailDto(wagePayroll);

            #region 导出部分

            // 创建一个新的Excel工作簿
            IWorkbook workbook = new XSSFWorkbook();

            // 创建一个新的工作表
            ISheet sheet = workbook.CreateSheet("Sheet1");
            // 创建列并设置最小宽度
            int columnWidth = 20;
            int columnIndex = 0;
            sheet.SetColumnWidth(columnIndex, columnWidth * 256); // 设定列宽度，参数为1/256个字符宽度

            // 自动调整列宽
            for (int i = 0; i < 8; i++)
            {
                // 自动调整列宽
                //sheet.AutoSizeColumn(i);
                sheet.SetColumnWidth(i, Math.Max(columnWidth * 256, sheet.GetColumnWidth(i)));
            }

            #region 表头

            // 合并行和列的方式设置单元格的边界
            CellRangeAddress region = new CellRangeAddress(0, 1, 0, 7);
            sheet.AddMergedRegion(region);
            // 创建一个新的行
            IRow row = sheet.CreateRow(0);
            ICell cell = row.CreateCell(0);
            // 创建字体对象
            IFont font = workbook.CreateFont();
            font.FontHeightInPoints = 20; // 设置字体大小为 12

            // 设置单元格样式
            ICellStyle cellStyle = workbook.CreateCellStyle();
            cellStyle.Alignment = HorizontalAlignment.Center;//居中
            cellStyle.SetFont(font);
            cell.CellStyle = cellStyle;
            // 设置单元格的值
            cell.SetCellValue($"{dto.ProjectName}{dto.Month.ToString("yyyy年MM月工资明细表")}");

            #endregion 表头

            #region 中间部分

            exportService.CellRange(sheet, workbook, 4, 0, $"劳务公司:{dto.CorpName}", $"总支出额:{dto.TotalExpenditure}", $"当月应考勤天数:{dto.Days}");
            exportService.CellRange(sheet, workbook, 6, 0, $"当月计价总额:{dto.TotalValuation}", $"剩余合同金额:{dto.RemainingAmount}", $"流水号:{dto.Code}");

            #endregion 中间部分

            #region 表格部分

            exportService.CellRange(sheet, workbook, 8, 0, "项目名称", "劳务班组", "姓名", "身份证号", "开户行", "银行卡号", "实际考勤天数", "申请发放金额");

            #endregion 表格部分

            int index = 9;
            string projectName = "";
            foreach (var item in dto.Details)
            {
                exportService.CellRange(sheet, workbook, index, 0, item.ProjectName, item.GroupName, item.PersonName, item.IdCard, item.BankName, item.PersonBankNo, item.AttendanceDays.ToString(), "");
                projectName = item.ProjectName;
                index++;
            }

            // 将工作簿保存到内存流中
            using (MemoryStream memoryStream = new MemoryStream())
            {
                workbook.Write(memoryStream);

                // 设置要下载的文件名
                string fileName = $"{dto.CorpName}{projectName}{dto.Month.ToString()}工资单模板.xlsx";

                // 返回文件内容结果
                return File(memoryStream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
            }

            #endregion 导出部分
        }

        /// <summary>
        /// 导出明细
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("exportDetails/{id}")]
        public IActionResult ExportDetail(int id)
        {
            WagePayroll? wagePayroll = wagePayrollService.Get(id);
            if (wagePayroll == null) throw new BusinessException("工资单信息不存在，请先申请工资单");
            wagePayrollService.Verify(wagePayroll);
            IEnumerable<WagePayrollDetail> details = payrollDetailService.GetPayrollDetailsByPayrollId(id);
            PayrollDetailDto dto = new();
            if (!details.Any())
                //无明细数据,查询当前项目参建企业的班组及成员
                throw new BusinessException("工资单明细信息不存在，请先导入工资明细");
            else
                //有明细数据
                dto = details.AsPayrollDetailDto(wagePayroll, applicationContext);

            // 创建一个新的Excel工作簿
            IWorkbook workbook = new XSSFWorkbook();

            // 创建一个新的工作表
            ISheet sheet = workbook.CreateSheet("Sheet1");
            // 创建列并设置最小宽度
            int columnWidth = 20;
            int columnIndex = 0;
            sheet.SetColumnWidth(columnIndex, columnWidth * 256); // 设定列宽度，参数为1/256个字符宽度

            // 自动调整列宽
            for (int i = 0; i < 8; i++)
            {
                // 自动调整列宽
                //sheet.AutoSizeColumn(i);
                sheet.SetColumnWidth(i, Math.Max(columnWidth * 256, sheet.GetColumnWidth(i)));
            }
            int index = 4;

            #region 表头

            // 合并行和列的方式设置单元格的边界
            CellRangeAddress region = new CellRangeAddress(0, 1, 0, 7);
            sheet.AddMergedRegion(region);
            // 创建一个新的行
            IRow row = sheet.CreateRow(0);
            ICell cell = row.CreateCell(0);
            // 创建字体对象
            IFont font = workbook.CreateFont();
            font.FontHeightInPoints = 20; // 设置字体大小为 12

            // 设置单元格样式
            ICellStyle cellStyle = workbook.CreateCellStyle();
            cellStyle.Alignment = HorizontalAlignment.Center;//居中
            cellStyle.SetFont(font);
            cell.CellStyle = cellStyle;
            // 设置单元格的值
            cell.SetCellValue($"{dto.ProjectName}{dto.Month.ToString("yyyy年MM月工资审批表")}");

            #endregion 表头

            #region 中间部分

            exportService.CellRange(sheet, workbook, index, 0, $"劳务公司:{dto.CorpName}", $"总支出额:{dto.TotalExpenditure}", $"当月应考勤天数:{dto.Days}");
            index += 2;
            exportService.CellRange(sheet, workbook, index, 0, $"当月计价总额:{dto.TotalValuation}", $"当月申请发放额:{dto.AmountApplied}", $"剩余合同金额:{dto.RemainingAmount}");
            index++;
            exportService.CellRange(sheet, workbook, index, 0, $"当月申请代发班组列表:");
            index++;

            #endregion 中间部分

            #region 表头部分

            exportService.CellRange(sheet, workbook, index, 0, "序号", "班组名称", "工种", "工资单人数", "申请发放金额");
            index++;

            #endregion 表头部分

            #region 数据部分

            IEnumerable<PayrollDetailsDto> wagePayrollDetailDtos = dto.Details;
            var data = wagePayrollDetailDtos.GroupBy(a => new { a.GroupName, a.WorkType }).Select(g => new
            {
                GroupName = g.Key.GroupName,
                WorkType = g.Key.WorkType,
                PersonCount = g.Count(),
                AmountApplied = g.Sum(a => a.AmountApplied),
            });
            int rows = 1;
            foreach (var item in data)
            {
                exportService.CellRange(sheet, workbook, index, 0, rows.ToString(), item.GroupName, item.WorkType, item.PersonCount.ToString(), item.AmountApplied?.ToString() ?? string.Empty);
                index++;
                rows++;
            }

            #endregion 数据部分

            exportService.CellRange(sheet, workbook, index, 0, $"劳务公司负责人签字：", $"计划合同部签字：");
            index++;
            exportService.CellRange(sheet, workbook, index, 0, $"财务部门签字：", $"项目经理签字：");
            index += 3;

            #region 表头

            // 合并行和列的方式设置单元格的边界
            CellRangeAddress region1 = new CellRangeAddress(index, index + 1, 0, 7);
            sheet.AddMergedRegion(region1);
            // 创建一个新的行
            row = sheet.CreateRow(index);
            cell = row.CreateCell(0);
            // 创建字体对象
            font = workbook.CreateFont();
            font.FontHeightInPoints = 20; // 设置字体大小为 12

            // 设置单元格样式
            cellStyle = workbook.CreateCellStyle();
            cellStyle.Alignment = HorizontalAlignment.Center;//居中
            cellStyle.SetFont(font);
            cell.CellStyle = cellStyle;
            // 设置单元格的值
            cell.SetCellValue($"{dto.ProjectName}{dto.Month.ToString("yyyy年MM月工资明细表")}");

            #endregion 表头

            index += 2;
            exportService.CellRange(sheet, workbook, index, 0, "项目名称", "劳务班组", "姓名", "身份证号", "开户行", "银行卡号", "实际考勤天数", "申请发放金额");

            #region 明细部分

            index++;
            decimal? personCountSun = 0;
            decimal? amountSum = 0;
            foreach (var item in dto.Details)
            {
                exportService.CellRange(sheet, workbook, index, 0, item.ProjectName, item.GroupName, item.PersonName, item.IdCard, item.BankName, item.PersonBankNo, item.AttendanceDays.ToString(), item.AmountApplied?.ToString() ?? string.Empty);
                personCountSun += item.AttendanceDays;
                amountSum += item.AmountApplied;
                index++;
            }
            exportService.CellRange(sheet, workbook, index, 0, "合计", "", "", "", "", "", personCountSun.ToString() ?? string.Empty, amountSum.ToString() ?? string.Empty);

            #endregion 明细部分

            // 将工作簿保存到内存流中
            using (MemoryStream memoryStream = new MemoryStream())
            {
                workbook.Write(memoryStream);

                // 设置要下载的文件名
                string fileName = $"{dto.CorpName}{dto.ProjectName}{dto.Month.ToString()}工资单明细.xlsx";

                // 返回文件内容结果
                return File(memoryStream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
            }
        }

        /// <summary>
        /// 导入工资单明细校验
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        [HttpPost("import")]
        public ApiResult<IEnumerable<PayrollDetailImportDto>> UploadExcel(IFormFile file)
        {
            IEnumerable<PayrollDetailImportDto> data = importService.VerifyPayrollExcelToList(applicationContext.CurrentProjectInfo.ProjectId, file);
            return ApiResult<IEnumerable<PayrollDetailImportDto>>.Success(data);
        }

        /// <summary>
        /// 新增工资单明细
        /// </summary>
        /// <param name="id"></param>
        /// <param name="forms"></param>
        /// <returns></returns>
        [HttpPost("addPayrollDetails/{id}")]
        public ApiResult AddWagePayrollDetails(int id, IEnumerable<PayrollDetailImportForm> forms)
        {
            var data = wagePayrollService.Get(id) ?? throw new BusinessException("工资单不存在");

            #region 验证数据权限

            ownAuthorizer.CheckAndThrowIfOwnAuthenticationFailed(data.ProjectId);

            #endregion 验证数据权限

            IEnumerable<WagePayrollDetail> payrollDetails = importService.VerifyPayrollDetailsToEntity(id, forms);
            payrollDetailService.Create(id, payrollDetails);
            return ApiResult.Success("操作成功");
        }
    }
}